"use strict";

Components.utils.import("resource://gre/modules/Services.jsm")
Components.utils.import("resource://gre/modules/NewTabUtils.jsm")
Components.utils.import("resource://gre/modules/PlacesUtils.jsm")

const   CMD_SELALL  = "gListbox.parentNode.view.selection.selectAll();"
const   col2dox     = { "tree-header-1": "url", "tree-header-3": "title" }
const   REASON_ERROR    = Components.interfaces.mozIStorageStatementCallback.REASON_ERROR
const   REASON_CANCELED = Components.interfaces.mozIStorageStatementCallback.REASON_CANCELED
const   sort2delay  = 111

var reason2placeholder = [ { url: "", title: "The browsing history has not been entirely read."},
 { url: "Only message digest.", title: "The original url for this hash is not found midst the browsing history."}
]

var gLabelNoItems   = "Nothing is selected.";
var gUnicodeConverter = null;
var gCryptoHash     = null; //  initialize before usage
var gBlockedLinks   = Object.keys(NewTabUtils.blockedLinks.links)
var gdoxContent     = [];
var gPendingStatement = null;
var gListbox        = null; //  <treechildren id="content-tree"

//  ** * It queries the history to retrieve the most frequently visited sites. */
let PlaceProvider = {

    anchor : window,    //  host

/** snippet from NewTabUtils.jsm
 * Calculate the MD5 hash for a string.
 * @param aValue, * The string to convert.
 * @return The base64 representation of the MD5 hash.   */
    toHash : function(aValue)
    {
        let thevalue = gUnicodeConverter.convertToByteArray(aValue);
            gCryptoHash.init(gCryptoHash.MD5);
            gCryptoHash.update(thevalue, thevalue.length);
        return gCryptoHash.finish(true);
    },

//  ****************************
//  MozIStorageStatementCallback
    handleResult: function (aResultSet)
    {
        let therow;
        try {
            while (therow = aResultSet.getNextRow())
            {
                let theurl = therow.getResultByIndex(1) || "";
                if (theurl.length)
                {
                    let thehash = this.toHash(theurl);
                    let noble   = therow.getResultByIndex(2) || "";
                    let therec  = { url: theurl, hash: thehash, title: noble }
                        this.appendItem(therec);
                }
            }
        }
        catch (err) {
            Components.utils.reportError(err)
        }
    },

    // Should we somehow handle this error?
    handleError: function (aError)  //  mozIStorageError
    {
        Components.utils.reportError(aError)    // is it right ?
    //  dump("_dvk_dbg_, handleError of query against Places.sqlite.\n");
    },

    handleCompletion: function (aReason)
    {
        if(!(this.anchor.closed))
            this.anchor.setTimeout( function(aReason) { blank2front(aReason) }, 1, aReason );
        this.anchor.gPendingStatement = null;
        this.anchor = null;
    //  dump("_dvk_dbg_, Completion Reason:\t"); dump(aReason); dump("\n");
    },
//  MozIStorageStatementCallback
//  ****************************

    appendItem : function(arecord)
    {
        let thekeys = this.anchor.gBlockedLinks;
        if((thekeys.indexOf(arecord.url) + 1)
           || (thekeys.indexOf(arecord.hash) + 1))
    //      if(!(this.anchor.closed))
                this.anchor.setTimeout( function(arecord)
                            { updateRecord(arecord) }, 0, arecord);
    },

    populateList: function ()
    {
    // Execute the query. consistent with neotab.js, then move 2 module
        let options = PlacesUtils.history.getNewQueryOptions();
    //          options.maxResults = HISTORY_RESULTS_LIMIT; //  _dvk_dbg_
            // Sort by frecency, descending.
            options.sortingMode = options.SORT_BY_FRECENCY_DESCENDING;
            options.resultType  = options.RESULTS_AS_URI;
        let query1 = PlacesUtils.history.getNewQuery();
        let thedb = PlacesUtils.history.QueryInterface(Components.interfaces.nsPIPlacesDatabase);
        let thepeen = thedb.asyncExecuteLegacyQueries([ query1 ], 1, options, this );
    //  dump("_dvk_dbg_, asyncExecuteLegacyQueries:\t"); dump(thepeen); dump("\n"); //  _dvk_dbg_
        return thepeen;
    }
};  //  let PlaceProvider = {

let gUpdaterEnface = {

    setSortDirect : function()
    {
        let thevalue = gListbox.getAttribute("sortResource");
        let thenode = (thevalue) ? document.getElementById(thevalue) : null;
        if(thenode)
        {
            thevalue = gListbox.getAttribute("sortDirection");
            thenode.setAttribute("sortDirection", thevalue);
        }
    },

    setSplitter : function(anode)
    {
        let thelist = anode.parentNode.querySelectorAll("splitter");
        for (let i = 0; i < thelist.length; ++i)
        if(anode.hidden) thelist[i].setAttribute("disabled", "true")
            else thelist[i].removeAttribute("disabled");
    },

    //  click and command
    handleEvent : function(anevt)
    {
        const tagsName = [ "menuitem", "treecol" ]
        let thenode = anevt.target;
        let thype = thenode.getAttribute("type");
        if(tagsName.indexOf(thenode.tagName) + 1)        
            if((anevt.type == "click") || (thype == "radio")) 
            {   //  arrangement: click or radio
                window.setTimeout( function() { gUpdaterEnface.setSortDirect() }, 1);
                let thenode = gListbox.parentNode.querySelector("treecols[pickertooltiptext]");
                    thenode.firstElementChild.removeAttribute("sortDirection");
                    thenode.lastElementChild.removeAttribute("sortDirection");
            }
            else //  display:  command or checkbox against menuitem
                window.setTimeout( function(anode)
                    { gUpdaterEnface.setSplitter(anode) }, 1,
                        document.getElementById("tree-header-3") )
        else
            if(anevt.originalTarget.tagName == tagsName[0])
                    window.setTimeout( function(anode)
                        { gUpdaterEnface.setSplitter(anode) }, 1,
                            document.getElementById("tree-header-3") )
    }
}   //  gUpdaterEnface

function updateAcceptBtn(asel)
{
    let theval = gLabelNoItems;
    let thebtn1 = document.getElementById("remove-cmd");
    let thebtn2 = document.getElementById("copy-cmd");
    if(asel.count)
    {
        thebtn2.removeAttribute("disabled");
        thebtn1.removeAttribute("disabled");
        var thattr = thebtn1.getAttribute("value");
            theval = [ thattr, asel.count ].join(" ");
    }
    else {
        thebtn1.setAttribute("disabled", "true");
        thebtn2.setAttribute("disabled", "true");
    }

    document.getElementById("status-button").label = theval;
}

function loadPlaceHolder()
{
    let thecode = function(anode, index)
    {
        anode = anode.firstElementChild;
    //  dump("_dvk_dbg_, title:\t"); dump(anode.lastElementChild.tagName); dump("\n");
        reason2placeholder[index].url = anode.firstElementChild.getAttribute("label");
        reason2placeholder[index].title = anode.lastElementChild.getAttribute("label");
    }
    let sequence = [ gListbox.firstElementChild, gListbox.lastElementChild ]
    thecode(sequence[0], 0);
    thecode(sequence[1], 1);
    gListbox.removeChild(sequence[0]);
    gListbox.removeChild(sequence[1]);
}

function dlgLoad(awin)
{
    var isMsgDigest = function(amsg)
    {
        const len2md5 = 24; //  const isNOTmd5 = /\.|\?|:|@/;
        if(amsg.length == len2md5)
        if((amsg.lastIndexOf("=") + 1) == len2md5)
            if(amsg.indexOf(":") == (-1))
                return true;
        return false;
    }

    gBlockedLinks = gBlockedLinks.filter(
        function(avalue)    //  only weight items
        {
            if(avalue)  //  test url or hash (24 letter's)
            if(avalue.length)
            if(avalue.length >> 2)
            {
                let theobj = { key: avalue, hash: "", url: "", title: "", fixed: true }
                if(isMsgDigest(avalue))
                {
                    theobj.hash = avalue;
                    theobj.fixed = false;
                }
                    else theobj.url = avalue;

                gdoxContent.push(theobj);
                return true;
            }
            return false;
        } );

    window.returnValue = false;
    gListbox = document.getElementById("content-tree");
    loadPlaceHolder();

    if((gBlockedLinks.length) && !(NewTabUtils.blockedLinks.isEmpty()))
    {
        gUnicodeConverter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"]
                    .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
        gUnicodeConverter.charset = 'utf8';
        gCryptoHash = Components.classes["@mozilla.org/security/hash;1"]
                    .createInstance(Components.interfaces.nsICryptoHash);
        gLabelNoItems = document.getElementById("status-button").label;

        window.addEventListener("unload", function() { cancelOps() }, false);
        gPendingStatement = PlaceProvider.populateList();  //  operation

        window.setTimeout( function(anode) { anode.focus() }, 1, gListbox.parentNode );
    //  oncommand="document.getElementById('main-tree').view.selection.selectAll();" />
        gdoxContent.forEach(loadListbox, gListbox);
        document.getElementById("selall-cmd").setAttribute("oncommand", CMD_SELALL);
        document.querySelector("menupopup > menu[hidden]").removeAttribute("hidden");

        let thevalue = gListbox.getAttribute("sortResource");
        let thenode = (thevalue) ? document.getElementById(thevalue) : null;
        if((thenode) && !(thenode.hidden))
        {
            gUpdaterEnface.setSortDirect();
            window.setTimeout( function(anode) { sortContent(anode) }, sort2delay, thenode );
            thevalue = gListbox.getAttribute("sortDirection");
            thevalue = (thevalue == "descending") ? "ascending" : "descending";
            gListbox.setAttribute("sortDirection", thevalue);
        }
        else {
            gListbox.removeAttribute("sortResource");
            gListbox.removeAttribute("sortDirection");
        }
    //  update enface
        thenode = gListbox.parentNode.querySelector("treecols[pickertooltiptext]");
        if(thenode.lastElementChild.hidden) gUpdaterEnface.setSplitter(thenode.lastElementChild);
            thenode.addEventListener("click", gUpdaterEnface, false);
    //      thenode.addEventListener("command", gUpdaterEnface, false);
        document.getElementById("view-submenu").addEventListener("command", gUpdaterEnface, false);
    }
    else
    {
        document.documentElement.querySelector("deck[flex]").selectedIndex = 1;
        document.getElementById("accept-btn").removeAttribute("tooltiptext");
        document.getElementById("selall-cmd").addEventListener("command",
            function() { document.querySelector("textbox[readonly]").select() }, false);
        document.getElementById("sub-menu").hidden = true;
        document.querySelector("textbox[readonly]").focus();
        gListbox.removeAttribute("sortResource");
        gListbox.removeAttribute("sortDirection");
    }
    document.getElementById("remove-cmd").addEventListener("command", function() { window.close() }, false);
}

function toggleCols(amenu)
{
    let thenode = document.getElementById(amenu.value);
    if(thenode) thenode.hidden = (amenu.getAttribute("checked") == "true");
}

function updateMenu(acontainer)
{
    //dump("_dvk_dbg_, updateMenu.value:\t"); dump(thenode.hidden); dump("\n");
    let thecol = gListbox.getAttribute("sortResource");
    let thelist = acontainer.querySelectorAll("menuitem");
    for (let i = 0; i < thelist.length; ++i)
    {
        let thype = thelist[i].getAttribute("type") || "";
        if(thype == "checkbox")
        {
            let thenode = document.getElementById(thelist[i].value);
            if(thenode.hidden)  thelist[i].removeAttribute("checked")
                else thelist[i].setAttribute("checked", "true");
        }
        else    //  asc : ascending or descending
        if(thype == "radio")
        {
            let theval = thelist[i].value;
            if(theval == thecol) thelist[i].setAttribute("checked", "true")
                else thelist[i].removeAttribute("checked");
            thelist[i].disabled = document.getElementById(theval).hidden;
        }
    }
}

function loadListbox(anitem)
{
//  first: label, second: value theline = this.appendItem(anitem.url, anitem.key);

    let thitem = document.createElement("treeitem");
        thitem.setAttribute("container", "false");
        thitem.label = anitem.key;

    let therow  = document.createElement("treerow");

    let cell = document.createElement("treecell");
        cell.setAttribute("label", anitem.url);
    therow.appendChild(cell);

        cell = document.createElement("treecell");
        cell.setAttribute("label", anitem.hash);
    therow.appendChild(cell);   //  plus title
    therow.appendChild(document.createElement("treecell"));

    thitem.appendChild(therow);
    this.appendChild(thitem);         
}

function sortContent(acol)
{
    let prevalue = gListbox.getAttribute("sortDirection")
        gListbox.removeAttribute("sortDirection");
        gListbox.removeAttribute("sortResource");

    let thattr = col2dox[acol.id];
    if(!thattr) return;

    let trend  = (prevalue == "ascending") ? (-1) : (+1);
    let cmpcode = function(afirst, anext)
        {
            let retval = (afirst[thattr] < anext[thattr]) ? (-1) : (+1);
                if(afirst[thattr] == anext[thattr]) retval = 0
                    else retval *= trend;
            return retval;
        }
    gdoxContent.sort(cmpcode);

    //      gdoxContent.forEach(updateListbox, gListbox);
    let thyield = [];
    let thesel = gListbox.parentNode.view.selection;
    let thelist = gListbox.querySelectorAll("treeitem");
    for (let i = 0; i < thelist.length; ++i)
    {
        if(thesel.isSelected(i)) thyield.push(thelist[i].label);
        thelist[i].label = gdoxContent[i].key
        let thecells = thelist[i].querySelectorAll("treecell");
            thecells[0].setAttribute("label", gdoxContent[i].url);
            thecells[1].setAttribute("label", gdoxContent[i].hash);
            thecells[2].setAttribute("label", gdoxContent[i].title || "");
    }

    if(thyield.length)
        window.setTimeout( function(asequence)
            { toggleRows(asequence) }, 0, thyield );

    trend = (trend < 0) ? "descending" : "ascending";
    gListbox.setAttribute("sortDirection", trend);
    gListbox.setAttribute("sortResource", acol.id);

    return;
}

function toggleRows(asequence)
{
    let thesel = gListbox.parentNode.view.selection;
    let thelist = gListbox.querySelectorAll("treeitem");
    for (let i = 0; i < thelist.length; ++i)
    {
        let toggle = thesel.isSelected(i);
        if(asequence.indexOf(thelist[i].label) + 1) toggle = !(toggle);
        if(toggle) thesel.toggleSelect(i);
    }    
}

function toggleWidth(anode)
{
    anode.firstElementChild.width = anode.lastElementChild.width;
    //  dump(anode.firstElementChild.width); dump("\n");
}

function sel2copy()
{
    let retval = [], header = [];
    try {
        let thetiles = gListbox.parentNode.querySelectorAll("treecols > treecol");
        for (let j = 0; j < thetiles.length; ++j)
            header.push(thetiles[j].getAttribute("label"));
        header.push(""); thetiles = null;

        let thesel = gListbox.parentNode.view.selection;
        let thelist = gListbox.querySelectorAll("treeitem");
        for (let i = 0; i < thelist.length; ++i)
        if(thesel.isSelected(i))
        {
    //  dump("_dvk_dbg_, querySelectorAll.treecell:\t"); dump(thetiles[j].tagName); dump("\n");
            let theobj = { }
            let thecells = thelist[i].querySelectorAll("treecell");
            for (let j = 0; j < thecells.length ; ++j)
                if(header[j]) theobj[(header[j])] = thecells[j].getAttribute("label")
                else break;

            retval.push( theobj );
        }

        const gClipboardHelper = Components.classes["@mozilla.org/widget/clipboardhelper;1"]
                                    .getService(Components.interfaces.nsIClipboardHelper);
        gClipboardHelper.copyString( JSON.stringify(retval, null, '\t') );
    }
    catch (err) {
        Components.utils.reportError(err)
    }
}

function blank2front(areason)
{
    if(areason == REASON_CANCELED) return;
    let index = (areason == REASON_ERROR) ? 0 : 1;
    let thelist = gListbox.querySelectorAll("treeitem");
    for (let i = 0; (i < thelist.length) && (i < gdoxContent.length); ++i)
    if(!(gdoxContent[i].fixed))
    {
        gdoxContent[i].url = reason2placeholder[index].url;
        gdoxContent[i].title = reason2placeholder[index].title;
        if(thelist[i].label == gdoxContent[i].key)  //  equal test
        {
            let thecells = thelist[i].querySelectorAll("treecell");
            if(gdoxContent[i].url) thecells[0].setAttribute("label", gdoxContent[i].url);
            thecells[2].setAttribute("label", gdoxContent[i].title);
        }
    }
    return;
}

    //  A record is queued by PlaceProvider
function updateRecord(arecord)
{
    let thefixed = true, theline = null;
    //  let thenode = document.getElementById("links-list");
    for each (let item in gdoxContent)
    if((item.key == arecord.hash) || (item.key == arecord.url))
    {
        theline = item, theline.fixed = true;
        theline.url = arecord.url;
        theline.hash = arecord.hash;
    }
    else
        if(!(item.fixed)) thefixed = false;

    if(thefixed) cancelOps();

    if(theline) theline.title = arecord.title
        else return;

    let thelist = gListbox.querySelectorAll("treeitem");
    for (let i = 0; i < thelist.length; ++i)
        if(thelist[i].label == theline.key)
    {
        let thecells = thelist[i].querySelectorAll("treecell");
            thecells[0].setAttribute("label", theline.url);
            thecells[1].setAttribute("label", theline.hash);
        if(theline.title)
            thecells[2].setAttribute("label", theline.title);
        break;
    }
    return;
}

function cancelOps()
{
    let thepeen = gPendingStatement || { "cancel": function() {} };
    try { thepeen.cancel() }
        finally { gPendingStatement = null }
}

function showCtxmenu(evt)
{
    if(!(evt.charCode))
    if(evt.keyCode === KeyEvent.DOM_VK_RETURN)
    {
        var themenu = document.getElementById("main-menu");
            themenu.openPopup( null, null,
                        (window.innerWidth >> 1), (window.innerHeight >> 1),
                               true, false);
        return false;
    }
    return true;
}

function sel2kill()
{
    let blockedLinks = NewTabUtils.blockedLinks.links;
    let thesel  = gListbox.parentNode.view.selection;
    let thelist = gListbox.querySelectorAll("treeitem");
    for (let i = 0; i < thelist.length; ++i)
        if(thesel.isSelected(i))
            if(thelist[i].label in blockedLinks)
                delete blockedLinks[ (thelist[i].label) ];
    NewTabUtils.blockedLinks.save();
    window.returnValue = true;
}
